{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Description:\n",
    "这里用Pytorch搭建AFM Model在cretio数据集上进行点击率预测的任务"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-01-16T16:52:02.195997Z",
     "start_time": "2021-01-16T16:52:00.675074Z"
    }
   },
   "outputs": [],
   "source": [
    "\"\"\"导入包\"\"\"\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "import matplotlib.pyplot as plt\n",
    "from tqdm import tqdm\n",
    "import datetime\n",
    "import itertools\n",
    "\n",
    "# pytorch\n",
    "import torch\n",
    "from torch.utils.data import DataLoader, Dataset, TensorDataset\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "import torch.optim as optim\n",
    "\n",
    "from torchkeras import summary, Model\n",
    "from sklearn.metrics import roc_auc_score\n",
    "\n",
    "import warnings\n",
    "warnings.filterwarnings('ignore')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 数据准备"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-01-16T16:52:02.211915Z",
     "start_time": "2021-01-16T16:52:02.197951Z"
    }
   },
   "outputs": [],
   "source": [
    "file_path = './preprocessed_data/'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-01-16T16:52:02.226915Z",
     "start_time": "2021-01-16T16:52:02.213931Z"
    }
   },
   "outputs": [],
   "source": [
    "def prepared_data(file_path):\n",
    "    # 读入训练集， 验证集和测试集\n",
    "    train = pd.read_csv(file_path + 'train_set.csv')\n",
    "    val = pd.read_csv(file_path + 'val_set.csv')\n",
    "    test = pd.read_csv(file_path + 'test_set.csv')\n",
    "    \n",
    "    trn_x, trn_y = train.drop(columns='Label').values, train['Label'].values\n",
    "    val_x, val_y = val.drop(columns='Label').values, val['Label'].values\n",
    "    test_x = test.values\n",
    "    \n",
    "    fea_col = np.load(file_path + 'fea_col.npy', allow_pickle=True)\n",
    "    \n",
    "    return fea_col, (trn_x, trn_y), (val_x, val_y), test_x"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-01-16T16:52:02.274791Z",
     "start_time": "2021-01-16T16:52:02.228868Z"
    }
   },
   "outputs": [],
   "source": [
    "\"\"\"导入数据\"\"\"\n",
    "fea_cols, (trn_x, trn_y), (val_x, val_y), test_x = prepared_data(file_path)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-01-16T16:52:02.290702Z",
     "start_time": "2021-01-16T16:52:02.275786Z"
    }
   },
   "outputs": [],
   "source": [
    "# 把数据构建成数据管道\n",
    "dl_train_dataset = TensorDataset(torch.tensor(trn_x).float(), torch.tensor(trn_y).float())\n",
    "dl_val_dataset = TensorDataset(torch.tensor(val_x).float(), torch.tensor(val_y).float())\n",
    "\n",
    "dl_train = DataLoader(dl_train_dataset, shuffle=True, batch_size=32)\n",
    "dl_val = DataLoader(dl_val_dataset, shuffle=True, batch_size=32)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-01-16T16:52:02.305664Z",
     "start_time": "2021-01-16T16:52:02.291702Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([32, 39]) tensor([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 1., 0., 1., 0., 0., 1.,\n",
      "        0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 1., 1., 0.])\n"
     ]
    }
   ],
   "source": [
    "# 看一眼数据\n",
    "for x, y in iter(dl_train):\n",
    "    print(x.shape, y)\n",
    "    break"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 构建模型\n",
    "这里依然是使用继承nn.Modult基类构建模型， 并辅助应用模型容器进行封装， AFM的模型架构如下：\n",
    "\n",
    "![](https://img-blog.csdnimg.cn/20210102204934171.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3d1emhvbmdxaWFuZw==,size_1,color_FFFFFF,t_70#pic_center)\n",
    "\n",
    "这个模型在NFM的基础上加入了Attention机制，  我们需要实现这个Attention网络， 不用DNN模块了。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-01-16T16:52:02.321664Z",
     "start_time": "2021-01-16T16:52:02.307658Z"
    },
    "run_control": {
     "marked": true
    }
   },
   "outputs": [],
   "source": [
    "class Dnn(nn.Module):\n",
    "    def __init__(self, hidden_units, dropout=0.):\n",
    "        \"\"\"\n",
    "        hidden_units: 列表， 每个元素表示每一层的神经单元个数， 比如[256, 128, 64], 两层网络， 第一层神经单元128， 第二层64， 第一个维度是输入维度\n",
    "        dropout = 0.\n",
    "        \"\"\"\n",
    "        super(Dnn, self).__init__()\n",
    "        \n",
    "        self.dnn_network = nn.ModuleList([nn.Linear(layer[0], layer[1]) for layer in list(zip(hidden_units[:-1], hidden_units[1:]))])\n",
    "        self.dropout = nn.Dropout(dropout)\n",
    "    \n",
    "    def forward(self, x):  \n",
    "        for linear in self.dnn_network:\n",
    "            x = linear(x)\n",
    "            x = F.relu(x)    \n",
    "        x = self.dropout(x) \n",
    "        return x"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-01-16T16:52:02.337579Z",
     "start_time": "2021-01-16T16:52:02.324612Z"
    }
   },
   "outputs": [],
   "source": [
    "class Attention_layer(nn.Module):\n",
    "    def __init__(self, att_units):\n",
    "        \"\"\"\n",
    "        :param att_units: [embed_dim, att_vector]\n",
    "        \"\"\"\n",
    "        super(Attention_layer, self).__init__()\n",
    "        \n",
    "        self.att_w = nn.Linear(att_units[0], att_units[1])\n",
    "        self.att_dense = nn.Linear(att_units[1], 1)\n",
    "    \n",
    "    def forward(self, bi_interaction):     # bi_interaction (None, (field_num*(field_num-1)_/2, embed_dim)\n",
    "        a = self.att_w(bi_interaction)    # (None, (field_num*(field_num-1)_/2, t)\n",
    "        a = F.relu(a)             # (None, (field_num*(field_num-1)_/2, t)\n",
    "        att_scores = self.att_dense(a)  # (None, (field_num*(field_num-1)_/2, 1)\n",
    "        att_weight = F.softmax(att_scores, dim=1)  #  (None, (field_num*(field_num-1)_/2, 1)\n",
    "\n",
    "        att_out = torch.sum(att_weight * bi_interaction, dim=1)   # (None, embed_dim)\n",
    "        return att_out     "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-01-16T16:52:02.369493Z",
     "start_time": "2021-01-16T16:52:02.339572Z"
    }
   },
   "outputs": [],
   "source": [
    "class AFM(nn.Module):\n",
    "    def __init__(self, feature_columns, mode, hidden_units, att_vector=8, dropout=0.5, useDNN=False):\n",
    "        \"\"\"\n",
    "        AFM:\n",
    "        :param feature_columns: 特征信息， 这个传入的是fea_cols array[0] dense_info  array[1] sparse_info\n",
    "        :param mode: A string, 三种模式, 'max': max pooling, 'avg': average pooling 'att', Attention\n",
    "        :param att_vector: 注意力网络的隐藏层单元个数\n",
    "        :param hidden_units: DNN网络的隐藏单元个数， 一个列表的形式， 列表的长度代表层数， 每个元素代表每一层神经元个数， lambda文里面没加\n",
    "        :param dropout: Dropout比率\n",
    "        :param useDNN: 默认不使用DNN网络\n",
    "        \"\"\"\n",
    "        super(AFM, self).__init__()\n",
    "        self.dense_feature_cols, self.sparse_feature_cols = feature_columns\n",
    "        self.mode = mode\n",
    "        self.useDNN = useDNN\n",
    "        \n",
    "        # embedding\n",
    "        self.embed_layers = nn.ModuleDict({\n",
    "            'embed_' + str(i): nn.Embedding(num_embeddings=feat['feat_num'], embedding_dim=feat['embed_dim'])\n",
    "            for i, feat in enumerate(self.sparse_feature_cols)\n",
    "        })\n",
    "        \n",
    "        # 如果是注意机制的话，这里需要加一个注意力网络\n",
    "        if self.mode == 'att':   \n",
    "            self.attention = Attention_layer([self.sparse_feature_cols[0]['embed_dim'], att_vector])\n",
    "            \n",
    "        # 如果使用DNN的话， 这里需要初始化DNN网络\n",
    "        if self.useDNN:\n",
    "            # 这里要注意Pytorch的linear和tf的dense的不同之处， 前者的linear需要输入特征和输出特征维度， 而传入的hidden_units的第一个是第一层隐藏的神经单元个数，这里需要加个输入维度\n",
    "            self.fea_num = len(self.dense_feature_cols) + self.sparse_feature_cols[0]['embed_dim']\n",
    "            hidden_units.insert(0, self.fea_num)\n",
    "\n",
    "            self.bn = nn.BatchNorm1d(self.fea_num)     \n",
    "            self.dnn_network = Dnn(hidden_units, dropout)\n",
    "            self.nn_final_linear = nn.Linear(hidden_units[-1], 1)\n",
    "        else:\n",
    "            self.fea_num = len(self.dense_feature_cols) + self.sparse_feature_cols[0]['embed_dim']\n",
    "            self.nn_final_linear = nn.Linear(self.fea_num, 1)\n",
    "    \n",
    "    def forward(self, x):\n",
    "        dense_inputs, sparse_inputs = x[:, :len(self.dense_feature_cols)], x[:, len(self.dense_feature_cols):]\n",
    "        sparse_inputs = sparse_inputs.long()       # 转成long类型才能作为nn.embedding的输入\n",
    "        sparse_embeds = [self.embed_layers['embed_'+str(i)](sparse_inputs[:, i]) for i in range(sparse_inputs.shape[1])]\n",
    "        sparse_embeds = torch.stack(sparse_embeds)     # embedding堆起来， (field_dim, None, embed_dim)\n",
    "        sparse_embeds = sparse_embeds.permute((1, 0, 2))\n",
    "        # 这里得到embedding向量之后 sparse_embeds(None, field_num, embed_dim)\n",
    "        # 下面进行两两交叉， 注意这时候不能加和了，也就是NFM的那个计算公式不能用， 这里两两交叉的结果要进入Attention\n",
    "        # 两两交叉enbedding之后的结果是一个(None, (field_num*field_num-1)/2, embed_dim) \n",
    "        # 这里实现的时候采用一个技巧就是组合\n",
    "        #比如fild_num有4个的话，那么组合embeding就是[0,1] [0,2],[0,3],[1,2],[1,3],[2,3]位置的embedding乘积操作\n",
    "        first = []\n",
    "        second = []\n",
    "        for f, s in itertools.combinations(range(sparse_embeds.shape[1]), 2):\n",
    "            first.append(f)\n",
    "            second.append(s)\n",
    "        # 取出first位置的embedding  假设field是3的话，就是[0, 0, 0, 1, 1, 2]位置的embedding\n",
    "        p = sparse_embeds[:, first, :]     # (None, (field_num*(field_num-1)_/2, embed_dim)\n",
    "        q = sparse_embeds[:, second, :]   # (None, (field_num*(field_num-1)_/2, embed_dim)\n",
    "        bi_interaction = p * q    # (None, (field_num*(field_num-1)_/2, embed_dim)\n",
    "        \n",
    "        if self.mode == 'max':\n",
    "            att_out = torch.sum(bi_interaction, dim=1)  #  (None, embed_dim)\n",
    "        elif self.mode == 'avg':\n",
    "            att_out = torch.mean(bi_interaction, dim=1)  # (None, embed_dim)\n",
    "        else:\n",
    "            # 注意力网络\n",
    "            att_out = self.attention(bi_interaction)  # (None, embed_dim)\n",
    "        \n",
    "        # 把离散特征和连续特征进行拼接\n",
    "        x = torch.cat([att_out, dense_inputs], dim=-1)\n",
    "        \n",
    "        if not self.useDNN:\n",
    "            outputs = F.sigmoid(self.nn_final_linear(x))\n",
    "        else:\n",
    "            # BatchNormalization\n",
    "            x = self.bn(x)\n",
    "            # deep\n",
    "            dnn_outputs = self.nn_final_linear(self.dnn_network(x))\n",
    "            outputs = F.sigmoid(dnn_outputs)\n",
    "        \n",
    "        return outputs"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-01-16T16:53:40.284511Z",
     "start_time": "2021-01-16T16:53:40.269505Z"
    }
   },
   "outputs": [],
   "source": [
    "# 建立模型\n",
    "hidden_units = [128, 64, 32]\n",
    "dnn_dropout = 0.\n",
    "\n",
    "model = AFM(fea_cols, 'att', hidden_units, dropout=dnn_dropout, useDNN=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-01-16T16:53:42.551000Z",
     "start_time": "2021-01-16T16:53:42.523518Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "----------------------------------------------------------------\n",
      "        Layer (type)               Output Shape         Param #\n",
      "================================================================\n",
      "         Embedding-1                    [-1, 8]             632\n",
      "         Embedding-2                    [-1, 8]           2,016\n",
      "         Embedding-3                    [-1, 8]          10,344\n",
      "         Embedding-4                    [-1, 8]           8,344\n",
      "         Embedding-5                    [-1, 8]             240\n",
      "         Embedding-6                    [-1, 8]              56\n",
      "         Embedding-7                    [-1, 8]           9,312\n",
      "         Embedding-8                    [-1, 8]             312\n",
      "         Embedding-9                    [-1, 8]              16\n",
      "        Embedding-10                    [-1, 8]           7,264\n",
      "        Embedding-11                    [-1, 8]           7,408\n",
      "        Embedding-12                    [-1, 8]           9,912\n",
      "        Embedding-13                    [-1, 8]           6,592\n",
      "        Embedding-14                    [-1, 8]             160\n",
      "        Embedding-15                    [-1, 8]           6,552\n",
      "        Embedding-16                    [-1, 8]           9,272\n",
      "        Embedding-17                    [-1, 8]              72\n",
      "        Embedding-18                    [-1, 8]           4,272\n",
      "        Embedding-19                    [-1, 8]           1,608\n",
      "        Embedding-20                    [-1, 8]              32\n",
      "        Embedding-21                    [-1, 8]           9,632\n",
      "        Embedding-22                    [-1, 8]              56\n",
      "        Embedding-23                    [-1, 8]              96\n",
      "        Embedding-24                    [-1, 8]           5,832\n",
      "        Embedding-25                    [-1, 8]             264\n",
      "        Embedding-26                    [-1, 8]           4,432\n",
      "           Linear-27               [-1, 325, 8]              72\n",
      "           Linear-28               [-1, 325, 1]               9\n",
      "      BatchNorm1d-29                   [-1, 21]              42\n",
      "           Linear-30                  [-1, 128]           2,816\n",
      "           Linear-31                   [-1, 64]           8,256\n",
      "           Linear-32                   [-1, 32]           2,080\n",
      "          Dropout-33                   [-1, 32]               0\n",
      "           Linear-34                    [-1, 1]              33\n",
      "================================================================\n",
      "Total params: 118,036\n",
      "Trainable params: 118,036\n",
      "Non-trainable params: 0\n",
      "----------------------------------------------------------------\n",
      "Input size (MB): 0.000149\n",
      "Forward/backward pass size (MB): 0.026024\n",
      "Params size (MB): 0.450272\n",
      "Estimated Total Size (MB): 0.476444\n",
      "----------------------------------------------------------------\n"
     ]
    }
   ],
   "source": [
    "summary(model, input_shape=(trn_x.shape[1],))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-01-16T16:53:45.698035Z",
     "start_time": "2021-01-16T16:53:45.676128Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[0.5366],\n",
      "        [0.5351],\n",
      "        [0.5415],\n",
      "        [0.5341],\n",
      "        [0.5343],\n",
      "        [0.5534],\n",
      "        [0.5365],\n",
      "        [0.5381],\n",
      "        [0.5502],\n",
      "        [0.5351],\n",
      "        [0.5348],\n",
      "        [0.5571],\n",
      "        [0.5373],\n",
      "        [0.5362],\n",
      "        [0.5345],\n",
      "        [0.5389],\n",
      "        [0.5379],\n",
      "        [0.5462],\n",
      "        [0.5422],\n",
      "        [0.5381],\n",
      "        [0.5354],\n",
      "        [0.5387],\n",
      "        [0.5361],\n",
      "        [0.5383],\n",
      "        [0.5382],\n",
      "        [0.5269],\n",
      "        [0.5370],\n",
      "        [0.5385],\n",
      "        [0.5331],\n",
      "        [0.5486],\n",
      "        [0.5386],\n",
      "        [0.5365]], grad_fn=<SigmoidBackward>)\n"
     ]
    }
   ],
   "source": [
    "# 测试一下模型\n",
    "for fea, label in iter(dl_train):\n",
    "    out = model(fea)\n",
    "    print(out)\n",
    "    break"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 模型的训练"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-01-16T16:53:48.552998Z",
     "start_time": "2021-01-16T16:53:48.549009Z"
    }
   },
   "outputs": [],
   "source": [
    "# 模型的相关配置\n",
    "def auc(y_pred, y_true):\n",
    "    pred = y_pred.data\n",
    "    y = y_true.data\n",
    "    return roc_auc_score(y, pred)\n",
    "\n",
    "loss_func = nn.BCELoss()\n",
    "optimizer = optim.Adam(params=model.parameters(), lr=0.001)\n",
    "metric_func = auc\n",
    "metric_name = 'auc'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-01-16T16:54:00.357397Z",
     "start_time": "2021-01-16T16:53:52.780914Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "start_training.........\n",
      "================================================================2021-01-17 00:53:52\n",
      "[step=10] loss: 0.713, auc: 0.506\n",
      "[step=20] loss: 0.679, auc: 0.522\n",
      "[step=30] loss: 0.647, auc: 0.495\n",
      "[step=40] loss: 0.615, auc: 0.512\n",
      "\n",
      "EPOCH=1, loss=0.615, auc = 0.512, val_loss=0.474, val_auc = 0.617\n",
      "\n",
      "================================================================================2021-01-17 00:53:53\n",
      "[step=10] loss: 0.513, auc: 0.559\n",
      "[step=20] loss: 0.501, auc: 0.630\n",
      "[step=30] loss: 0.505, auc: 0.654\n",
      "[step=40] loss: 0.500, auc: 0.661\n",
      "\n",
      "EPOCH=2, loss=0.500, auc = 0.661, val_loss=0.437, val_auc = 0.624\n",
      "\n",
      "================================================================================2021-01-17 00:53:54\n",
      "[step=10] loss: 0.476, auc: 0.730\n",
      "[step=20] loss: 0.487, auc: 0.743\n",
      "[step=30] loss: 0.483, auc: 0.718\n",
      "[step=40] loss: 0.483, auc: 0.714\n",
      "\n",
      "EPOCH=3, loss=0.483, auc = 0.714, val_loss=0.430, val_auc = 0.615\n",
      "\n",
      "================================================================================2021-01-17 00:53:55\n",
      "[step=10] loss: 0.504, auc: 0.709\n",
      "[step=20] loss: 0.465, auc: 0.724\n",
      "[step=30] loss: 0.459, auc: 0.745\n",
      "[step=40] loss: 0.475, auc: 0.738\n",
      "\n",
      "EPOCH=4, loss=0.475, auc = 0.738, val_loss=0.440, val_auc = 0.567\n",
      "\n",
      "================================================================================2021-01-17 00:53:56\n",
      "[step=10] loss: 0.464, auc: 0.759\n",
      "[step=20] loss: 0.454, auc: 0.761\n",
      "[step=30] loss: 0.466, auc: 0.746\n",
      "[step=40] loss: 0.468, auc: 0.749\n",
      "\n",
      "EPOCH=5, loss=0.468, auc = 0.749, val_loss=0.435, val_auc = 0.629\n",
      "\n",
      "================================================================================2021-01-17 00:53:57\n",
      "[step=10] loss: 0.471, auc: 0.735\n",
      "[step=20] loss: 0.448, auc: 0.774\n",
      "[step=30] loss: 0.456, auc: 0.761\n",
      "[step=40] loss: 0.456, auc: 0.761\n",
      "\n",
      "EPOCH=6, loss=0.456, auc = 0.761, val_loss=0.439, val_auc = 0.657\n",
      "\n",
      "================================================================================2021-01-17 00:53:58\n",
      "[step=10] loss: 0.415, auc: 0.779\n",
      "[step=20] loss: 0.416, auc: 0.783\n",
      "[step=30] loss: 0.431, auc: 0.791\n",
      "[step=40] loss: 0.439, auc: 0.783\n",
      "\n",
      "EPOCH=7, loss=0.439, auc = 0.783, val_loss=0.452, val_auc = 0.629\n",
      "\n",
      "================================================================================2021-01-17 00:53:59\n",
      "[step=10] loss: 0.395, auc: 0.826\n",
      "[step=20] loss: 0.399, auc: 0.814\n",
      "[step=30] loss: 0.423, auc: 0.803\n",
      "[step=40] loss: 0.423, auc: 0.806\n",
      "\n",
      "EPOCH=8, loss=0.423, auc = 0.806, val_loss=0.467, val_auc = 0.643\n",
      "\n",
      "================================================================================2021-01-17 00:54:00\n",
      "Finished Training\n"
     ]
    }
   ],
   "source": [
    "# 脚本训练风格\n",
    "epochs = 8\n",
    "log_step_freq = 10\n",
    "\n",
    "dfhistory = pd.DataFrame(columns=['epoch', 'loss', metric_name, 'val_loss', 'val_'+metric_name])\n",
    "\n",
    "print('start_training.........')\n",
    "nowtime = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')\n",
    "print('========'*8 + '%s' %nowtime)\n",
    "\n",
    "for epoch in range(1, epochs+1):\n",
    "    \n",
    "    # 训练阶段\n",
    "    model.train()\n",
    "    loss_sum = 0.0\n",
    "    metric_sum = 0.0\n",
    "    step = 1\n",
    "    \n",
    "    for step, (features, labels) in enumerate(dl_train, 1):\n",
    "        # 梯度清零\n",
    "        optimizer.zero_grad()\n",
    "        \n",
    "        # 正向传播\n",
    "        predictions = model(features);\n",
    "        loss = loss_func(predictions, labels)\n",
    "        try:\n",
    "            metric = metric_func(predictions, labels)\n",
    "        except ValueError:\n",
    "            pass\n",
    "        \n",
    "        # 反向传播\n",
    "        loss.backward()\n",
    "        optimizer.step()\n",
    "        \n",
    "        # 打印batch级别日志\n",
    "        loss_sum += loss.item()\n",
    "        metric_sum += metric.item()\n",
    "        if step % log_step_freq == 0:\n",
    "            print((\"[step=%d] loss: %.3f, \" + metric_name + \": %.3f\") % (step, loss_sum/step, metric_sum/step));\n",
    "    \n",
    "    # 验证阶段\n",
    "    model.eval()\n",
    "    val_loss_sum = 0.0\n",
    "    val_metric_sum = 0.0\n",
    "    val_step = 1\n",
    "    \n",
    "    for val_step, (features, labels) in enumerate(dl_val, 1):\n",
    "        with torch.no_grad():\n",
    "            predictions = model(features)\n",
    "            val_loss = loss_func(predictions, labels)\n",
    "            try:\n",
    "                val_metric = metric_func(predictions, labels)\n",
    "            except ValueError:\n",
    "                pass\n",
    "        \n",
    "        val_loss_sum += val_loss.item()\n",
    "        val_metric_sum += val_metric.item()\n",
    "    \n",
    "    # 记录日志\n",
    "    info = (epoch, loss_sum/step, metric_sum/step, val_loss_sum/val_step, val_metric_sum/val_step)\n",
    "    dfhistory.loc[epoch-1] = info\n",
    "    \n",
    "    # 打印日志\n",
    "    print((\"\\nEPOCH=%d, loss=%.3f, \" + metric_name + \" = %.3f, val_loss=%.3f, \" + \"val_\" + metric_name + \" = %.3f\") %info)\n",
    "    nowtime = datetime.datetime.now().strftime(\"%Y-%m-%d %H:%M:%S\")\n",
    "    print('\\n' + '=========='* 8 + '%s' %nowtime)\n",
    "    \n",
    "print('Finished Training')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-01-16T16:52:09.337501Z",
     "start_time": "2021-01-16T16:52:09.307507Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>epoch</th>\n",
       "      <th>loss</th>\n",
       "      <th>auc</th>\n",
       "      <th>val_loss</th>\n",
       "      <th>val_auc</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <td>0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.717326</td>\n",
       "      <td>0.378402</td>\n",
       "      <td>0.707316</td>\n",
       "      <td>0.339038</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>1</td>\n",
       "      <td>2.0</td>\n",
       "      <td>0.691769</td>\n",
       "      <td>0.401785</td>\n",
       "      <td>0.673586</td>\n",
       "      <td>0.340718</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>2</td>\n",
       "      <td>3.0</td>\n",
       "      <td>0.658877</td>\n",
       "      <td>0.437010</td>\n",
       "      <td>0.628479</td>\n",
       "      <td>0.416281</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>3</td>\n",
       "      <td>4.0</td>\n",
       "      <td>0.618143</td>\n",
       "      <td>0.504018</td>\n",
       "      <td>0.575567</td>\n",
       "      <td>0.421431</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>4</td>\n",
       "      <td>5.0</td>\n",
       "      <td>0.576610</td>\n",
       "      <td>0.553058</td>\n",
       "      <td>0.529029</td>\n",
       "      <td>0.457773</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>5</td>\n",
       "      <td>6.0</td>\n",
       "      <td>0.545148</td>\n",
       "      <td>0.625388</td>\n",
       "      <td>0.493273</td>\n",
       "      <td>0.513164</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>6</td>\n",
       "      <td>7.0</td>\n",
       "      <td>0.524586</td>\n",
       "      <td>0.657078</td>\n",
       "      <td>0.473878</td>\n",
       "      <td>0.531904</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>7</td>\n",
       "      <td>8.0</td>\n",
       "      <td>0.513703</td>\n",
       "      <td>0.710741</td>\n",
       "      <td>0.461418</td>\n",
       "      <td>0.563895</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   epoch      loss       auc  val_loss   val_auc\n",
       "0    1.0  0.717326  0.378402  0.707316  0.339038\n",
       "1    2.0  0.691769  0.401785  0.673586  0.340718\n",
       "2    3.0  0.658877  0.437010  0.628479  0.416281\n",
       "3    4.0  0.618143  0.504018  0.575567  0.421431\n",
       "4    5.0  0.576610  0.553058  0.529029  0.457773\n",
       "5    6.0  0.545148  0.625388  0.493273  0.513164\n",
       "6    7.0  0.524586  0.657078  0.473878  0.531904\n",
       "7    8.0  0.513703  0.710741  0.461418  0.563895"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "dfhistory"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-01-16T16:54:06.421822Z",
     "start_time": "2021-01-16T16:54:06.146560Z"
    }
   },
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "def plot_metric(dfhistory, metric):\n",
    "    train_metrics = dfhistory[metric]\n",
    "    val_metrics = dfhistory['val_'+metric]\n",
    "    epochs = range(1, len(train_metrics) + 1)\n",
    "    plt.plot(epochs, train_metrics, 'bo--')\n",
    "    plt.plot(epochs, val_metrics, 'ro-')\n",
    "    plt.title('Training and validation '+ metric)\n",
    "    plt.xlabel(\"Epochs\")\n",
    "    plt.ylabel(metric)\n",
    "    plt.legend([\"train_\"+metric, 'val_'+metric])\n",
    "    plt.show()\n",
    "\n",
    "\n",
    "# 观察损失和准确率的变化\n",
    "plot_metric(dfhistory,\"loss\")\n",
    "plot_metric(dfhistory,\"auc\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-01-16T16:54:12.414709Z",
     "start_time": "2021-01-16T16:54:12.389776Z"
    }
   },
   "outputs": [],
   "source": [
    "# 预测\n",
    "y_pred_probs = model(torch.tensor(test_x).float())\n",
    "y_pred = torch.where(y_pred_probs>0.5, torch.ones_like(y_pred_probs), torch.zeros_like(y_pred_probs))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-01-16T16:54:13.737249Z",
     "start_time": "2021-01-16T16:54:13.716347Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [1.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.]])"
      ]
     },
     "execution_count": 32,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "y_pred"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-01-16T16:54:22.238459Z",
     "start_time": "2021-01-16T16:54:22.203513Z"
    }
   },
   "outputs": [],
   "source": [
    "# 模型的保存与使用\n",
    "torch.save(model, './model/AFM.pkl')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-01-16T16:54:23.930820Z",
     "start_time": "2021-01-16T16:54:23.903846Z"
    }
   },
   "outputs": [],
   "source": [
    "net_clone = torch.load('./model/AFM.pkl')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-01-16T16:54:24.605967Z",
     "start_time": "2021-01-16T16:54:24.579040Z"
    }
   },
   "outputs": [],
   "source": [
    "y_pred_probs = model(torch.tensor(test_x).float())\n",
    "y_pred = torch.where(y_pred_probs>0.5, torch.ones_like(y_pred_probs), torch.zeros_like(y_pred_probs))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-01-16T16:54:25.582517Z",
     "start_time": "2021-01-16T16:54:25.567557Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [1.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [1.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [0.],\n",
       "        [1.],\n",
       "        [0.]])"
      ]
     },
     "execution_count": 36,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "y_pred"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": false
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
